/*
 *  Copyright 2013 HCIWriter
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.hci.writer.client.ui;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;

import com.github.gwtbootstrap.client.ui.Button;
import com.github.gwtbootstrap.client.ui.Divider;
import com.github.gwtbootstrap.client.ui.Form;
import com.github.gwtbootstrap.client.ui.NavHeader;
import com.github.gwtbootstrap.client.ui.NavLink;
import com.github.gwtbootstrap.client.ui.NavList;
import com.github.gwtbootstrap.client.ui.ProgressBar;
import com.github.gwtbootstrap.client.ui.Row;
import com.github.gwtbootstrap.client.ui.TextArea;
import com.github.gwtbootstrap.client.ui.base.ProgressBarBase.Style;
import com.github.gwtbootstrap.client.ui.constants.FormType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Widget;
import com.hci.writer.shared.ExerciseStep;
import com.hci.writer.shared.Participant;
import com.hci.writer.shared.Task;

public class AdminViewImpl extends Composite implements AdminView {
	private static final long KEYSTROKE_SKIP_TIME = Long.MAX_VALUE;
	
	@UiField NavList navList;
	@UiField TextArea txtInput;
	@UiField Row rowStat;
	@UiField Label labStroke;
	@UiField Label labTime;
	@UiField Label labQuotient;
	@UiField Label labOverall;
	@UiField Label labErr;
	@UiField CheckBox chkMsgBox;
	@UiField ListBox lstSleepTime;
	
	private static AdminViewImplUiBinder uiBinder = GWT
			.create(AdminViewImplUiBinder.class);

	interface AdminViewImplUiBinder extends UiBinder<Widget, AdminViewImpl> {
	}

	private Presenter presenter;
	private Iterator<ExerciseStep> exIt;
	private ExerciseStep exerciseStep;
	private String nextText;
	private Date replayStart;
	private Date realStart;
	private Timer nextStepTimer;
	private boolean lastStrokeCorrectedText = false;
	private int errCnt = 0;
	
	private int noStrokes;
	private long strokeTime;
	private Date lastStrokeDate;
	
	public AdminViewImpl() {
		initWidget(uiBinder.createAndBindUi(this));
	}

	@Override
	public void setPresenter(Presenter presenter) {
		this.presenter = presenter;
	}
	
	@Override
	public	void showParticipants(ArrayList<String> participants) {
		// If a replay is actually playing stop this
		if(this.nextStepTimer != null) {
			this.nextStepTimer.cancel();
			this.nextStepTimer = null;
		}
		
		// Set replay elements invisible
		navList.setVisible(true);
		txtInput.setVisible(false);
		rowStat.setVisible(false);
		
		navList.clear();
		navList.add(new NavHeader("Recorded participants"));
		navList.add(new Divider());
		for(String s : participants) {
			NavLink navLink = new NavLink(s);
			navLink.addClickHandler(new LoadTaskListClickHandler(s));
			navList.add(navLink);
		}
		navList.add(new Divider());
		
		Form form = new Form();
		form.setType(FormType.HORIZONTAL);
		form.setAction("javascript:;");
		Button btnReload = new Button();
		btnReload.setText("Reload list");
		btnReload.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				presenter.loadParticipantlist();
			}
		});
		form.add(btnReload);
		navList.add(form);
	}
	
	@Override
	public void showProgress(String text) {
		navList.setVisible(true);
		txtInput.setVisible(false);
		rowStat.setVisible(false);
		navList.clear();
		navList.add(new NavHeader(text));
		navList.add(new Divider());
		ProgressBar progressBar = new ProgressBar();
		progressBar.setType(Style.ANIMATED);
		progressBar.setPercent(100);
		navList.add(progressBar);
		navList.add(new Divider());
	}

	@Override
	public void showTasks(Participant participant) {
		navList.setVisible(true);
		txtInput.setVisible(false);
		rowStat.setVisible(false);
		navList.clear();
		
		String header = "Solved tasks : " + participant.getKey();
		
		try {
			int keyMod = Integer.parseInt(participant.getKey())%4;
			if(keyMod == 0) {
				header += ", Keyboard sequence : HW => SW, Task sequence : A => B";
			} else if(keyMod == 1) {
				header += ", Keyboard sequence : HW => SW, Task sequence : B => A";
			} else if(keyMod == 2) {
				header += ", Keyboard sequence : SW => HW, Task sequence : A => B";
			} else if(keyMod == 3) {
				header += ", Keyboard sequence : SW => HW, Task sequence : B => A";
			}
		} catch (NumberFormatException e) {
			header += ", ke is not int => no info about keyboard and task sequence";
		}
		
		navList.add(new NavHeader(header));
		navList.add(new Divider());
		for(int i = 0; i < participant.getNumberOfTasks(); ++i) {
			Task task = participant.getTask(i);
			String taskName = task.getTaskName();
			NavLink navLink = new NavLink(taskName);
			navLink.addClickHandler(new LoadTaskClickHandler(taskName));
			navList.add(navLink);
		}
		navList.add(new Divider());
		
		Form form = new Form();
		form.setType(FormType.HORIZONTAL);
		form.setAction("javascript:;");
		Button btnReload = new Button();
		btnReload.setText("Reload list");
		btnReload.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				presenter.loadTaskList();
			}
		});
		form.add(btnReload);

		Button btnBack = new Button();
		btnBack.setText("Back to participants list");
		btnBack.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				presenter.loadParticipantlist();
			}
		});
		form.add(btnBack);

		
		navList.add(form);
	}
	
	@Override
	public void replayTask(Task task) {
		// If a replay is actually playing stop this
		if(this.nextStepTimer != null) {
			this.nextStepTimer.cancel();
			this.nextStepTimer = null;
		}
		
		// Set replay elements visible
		navList.setVisible(true);
		txtInput.setVisible(true);
		rowStat.setVisible(true);
		
		// Reset counters and set initial environment
		this.noStrokes = 0;
		this.strokeTime = 0;
		updateStat();
		this.nextText = task.getInitialText();
		txtInput.setText(this.nextText);
		txtInput.setCursorPos(0);

		// Copy references of all made steps to have a list that can be cleaned after that
		ArrayList<ExerciseStep> cleanedSteps = new ArrayList<ExerciseStep>();
		for(ExerciseStep es : task.steps) {
			cleanedSteps.add(es);
		}
		
		// Clean the steps from beginning
		while(cleanedSteps.size() > 2) {
			if(cleanedSteps.get(0).contentGap.trim().length() == 0) {
				StringBuilder newTextBuilder = new StringBuilder();
				newTextBuilder.append(this.nextText.substring(0, 
						cleanedSteps.get(0).contentBegin));
				newTextBuilder.append(cleanedSteps.get(0).contentGap);
				newTextBuilder.append(this.nextText.substring(this.nextText.length()-cleanedSteps.get(0).contentEnd));
				this.nextText = newTextBuilder.toString();
				
				txtInput.setText(this.nextText);
				
				cleanedSteps.remove(0);
			} else {
				break;
			}
		}
		
		// Clean the steps from the end
		while(cleanedSteps.size() > 2) {
			if(cleanedSteps.get(cleanedSteps.size()-1).contentBegin + cleanedSteps.get(cleanedSteps.size()-1).contentEnd + cleanedSteps.get(cleanedSteps.size()-1).contentGap.length() == cleanedSteps.get(cleanedSteps.size()-2).contentBegin + cleanedSteps.get(cleanedSteps.size()-2).contentEnd + cleanedSteps.get(cleanedSteps.size()-2).contentGap.length()) {
				cleanedSteps.remove(cleanedSteps.size()-1);
			} else {
				break;
			}
		}
		
		
		int sec = (int)((task.steps.get(task.steps.size()-1).date.getTime()-task.steps.get(0).date.getTime())/1000);
		int min = sec/60;
		sec = sec-min*60;
		if(sec < 10) {
			labOverall.setText("totaltime: " + min + ":0" + sec);
		} else {
			labOverall.setText("totaltime: " + min + ":" + sec);
		}
		
		this.exIt = cleanedSteps.iterator();
		
		if(exIt.hasNext()) {
			this.exerciseStep = exIt.next();
			this.replayStart = this.exerciseStep.date;
			this.lastStrokeDate = this.exerciseStep.date;
			this.realStart = new Date();
			this.lastStrokeCorrectedText = false;
			this.errCnt = 0;

			doStep();
		}
		
	}
	
	private void updateStat() {
		labStroke.setText("keystrokes: " + this.noStrokes);
		
		int sec = (int)(this.strokeTime/1000);
		int min = sec/60;
		sec = sec-(min*60);
		if(sec < 10) {
			labTime.setText("elapsed time: " + min + ":0" + sec);
		} else {
			labTime.setText("elapsed time: " + min + ":" + sec);
		}
		
		if(this.noStrokes == 0) {
			labQuotient.setText("time/keystroke: 0");
		} else {
			labQuotient.setText("time/keystroke: " + NumberFormat.getFormat("#.###").format(((double)this.strokeTime)/1000.0/((double)this.noStrokes)));
		}
		
		labErr.setText("errors: " + this.errCnt);
	}

	private void doStep() {
		String lastText = this.nextText;
		
		StringBuilder newTextBuilder = new StringBuilder();
		newTextBuilder.append(this.nextText.substring(0, 
				this.exerciseStep.contentBegin));
		newTextBuilder.append(this.exerciseStep.contentGap);
		newTextBuilder.append(this.nextText.substring(this.nextText.length()-this.exerciseStep.contentEnd));
		this.nextText = newTextBuilder.toString();
		
		// Check if size of text increased
		if (lastText.length() < this.nextText.length()) {
			this.lastStrokeCorrectedText = false;
		} else {
			if (lastText.length() > this.nextText.length()) {
				if (!this.lastStrokeCorrectedText) {
					if(chkMsgBox.getValue())
						Window.alert("Text will decrease");
					this.lastStrokeCorrectedText = true;
					++this.errCnt;
				}
			} else {
				if (!lastText.equals(this.nextText)) {
					if (!this.lastStrokeCorrectedText) {
						if(chkMsgBox.getValue())
							Window.alert("String will be replaced");
						this.lastStrokeCorrectedText = true;
						++this.errCnt;
					}
				} else {
					this.lastStrokeCorrectedText = false;
				}
			}
		}
		
		long timeBetweenStroke = this.exerciseStep.date.getTime()
				- this.lastStrokeDate.getTime();
		if (timeBetweenStroke <= KEYSTROKE_SKIP_TIME) {
			if (!lastText.equals(this.nextText)) {
				int diff = this.nextText.length() - lastText.length();
				if (diff < 0) {
					diff = diff * (-1);
				} else if (diff == 0) {
					diff = 1;
				}

				this.noStrokes += diff;
				this.strokeTime += timeBetweenStroke;
				this.lastStrokeDate = this.exerciseStep.date;

				updateStat();
			}
		} else {
			this.lastStrokeDate = this.exerciseStep.date;
		}
		
		txtInput.setText(this.nextText);
		txtInput.setFocus(true);
		txtInput.setCursorPos(this.exerciseStep.cursor);
		if(this.exerciseStep.selectionLength > 0)
			txtInput.setSelectionRange(this.exerciseStep.cursor, this.exerciseStep.selectionLength);
		
		if(exIt.hasNext()) {
			this.exerciseStep = exIt.next();
			Long delay = (this.exerciseStep.date.getTime()-this.replayStart.getTime())-(new Date().getTime()-this.realStart.getTime());

			if(delay <= 0) {
				doStep();
			} else {
				this.nextStepTimer = new Timer() {
					@Override
					public void run() {
						doStep();
					}
				};
				
				try {
					int customSleepTime = Integer.parseInt(this.lstSleepTime.getValue(this.lstSleepTime.getSelectedIndex()));
					if(customSleepTime >= 0) {
						this.nextStepTimer.schedule(customSleepTime);
					} else {
						this.nextStepTimer.schedule(delay.intValue());
					}
				} catch (IndexOutOfBoundsException e) {
					this.nextStepTimer.schedule(delay.intValue());
				} catch (NumberFormatException e) {
					this.nextStepTimer.schedule(delay.intValue());
				}
			}
		} else {
			this.nextStepTimer = null;
			Window.alert("Replay finished!");
		}
	}
	
	private class LoadTaskListClickHandler implements ClickHandler {
		String key;
		
		LoadTaskListClickHandler(String key) {
			this.key = key;
		}
		
		@Override
		public void onClick(ClickEvent event) {
			presenter.loadTaskList(this.key);
		}
	}
	
	private class LoadTaskClickHandler implements ClickHandler {
		String task;
		
		LoadTaskClickHandler(String task) {
			this.task = task;
		}
		
		@Override
		public void onClick(ClickEvent event) {
			presenter.loadTask(this.task);
		}
	}
}
